package pl.edu.agh.anomalyids.lerad.rule;

import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.apache.commons.lang3.tuple.Pair;

import pl.edu.agh.anomalyids.lerad.aspect.AbstractAspect;
import pl.edu.agh.anomalyids.lerad.aspect.enums.AspectType;
import pl.edu.agh.anomalyids.lerad.aspect.enums.Match;
import pl.edu.agh.anomalyids.network.Packet;

public class RulesHolder implements java.io.Serializable {
	private static final long serialVersionUID = -4920642399417956773L;

	final private HashMap<AspectType, HashMap<Object, HashSet<Rule>>> rulesMap;

	final private HashSet<Rule> rules;

	private static final double THRESHOLD = 10E12;

	public RulesHolder() {
		rulesMap = new HashMap<AspectType, HashMap<Object, HashSet<Rule>>>();
		rules = new HashSet<Rule>();
	}

	/**
	 * Adds rule to this holder.
	 * 
	 * @param rule
	 *            rule to add
	 */
	public void addRule(Rule rule) {
		for (AbstractAspect<?> aspect : rule.getConditionals()) {
			addAspect(rule, aspect);
		}
		rules.add(rule);
	}

	/**
	 * Removes rule from this holder.
	 * 
	 * @param rule
	 *            rule to remove
	 */
	public void delRule(Rule rule) {
		for (AbstractAspect<?> aspect : rule.getConditionals()) {
			delAspect(aspect);
		}
		rules.remove(rule);
	}

	/**
	 * Getter.
	 * 
	 * @return list of contained rules
	 */
	public Set<Rule> getRules() {
		return rules;
	}

	/**
	 * Adds rules from passed list to this holder.
	 * 
	 * @param rules
	 *            list with rules
	 */
	public void addRules(List<Rule> rules) {
		for (Rule rule : rules) {
			addRule(rule);
		}
	}

	private void addAspect(Rule rule, AbstractAspect<?> aspect) {
		HashMap<Object, HashSet<Rule>> map = rulesMap.get(aspect.getType());
		HashSet<Rule> set = null;
		if (map == null) {
			map = new HashMap<Object, HashSet<Rule>>();
			set = new HashSet<Rule>();
		} else {
			set = map.get(aspect.getData());
			if (set == null) {
				set = new HashSet<Rule>();
			}
		}
		set.add(rule);
		map.put(aspect.getData(), set);
		rulesMap.put(aspect.getType(), map);
	}

	private <T> void delAspect(AbstractAspect<?> aspect) {
		HashMap<Object, HashSet<Rule>> map = rulesMap.get(aspect.getType());
		if (map != null) {
			HashSet<Rule> set = map.get(aspect.getData());
			if (set != null) {
				set.remove(aspect);
				if (set.isEmpty()) {
					map.remove(aspect.getData());
				}
			}
		}
	}

	/**
	 * 
	 * Tries to find a matching rule.
	 * 
	 * @param rule
	 * 
	 * @return IRule instance in case of finding a match, null otherwise.
	 */
	public Pair<Match, Double> passesRule(Packet packet) {
		Set<Rule> matches = new HashSet<Rule>();
		double score = 0;
		for (AspectType aspectType : rulesMap.keySet()) {
			Pair<Match, List<Rule>> temp = RulesMatcher.checkByAspect(
					aspectType, packet, rulesMap);
			if (temp.getLeft().equals(Match.SAME)) {
				return Pair.of(Match.SAME, 0.0);
			}
			matches.addAll(temp.getRight());
			for (Rule rule : temp.getRight()) {
				score += rule.getTimeElapsed() * rule.getBelief()
						* rule.getWeight();
			}
			if (score >= THRESHOLD) {
				for (Rule rule : temp.getRight()) {
					rule.resetTimer();
				}
				return Pair.of(Match.DIFFER, score);
			}
		}
		if (matches.isEmpty()) {
			return Pair.of(Match.UNKNOWN, 0.0);
		}
		return Pair.of(Match.SAME, score);

	}

	@Override
	public String toString() {
		String result = "";
		for (Rule rule : rules) {
			result += rule.toString() + "\n";
		}
		return result;
	}

}
